#pragma once

class AIDPidl
{
public:
   PIDLIST_ABSOLUTE m_p;

   // Constructor/Destructor
   
   AIDPidl() : m_p(NULL)
   {
   }

   ~AIDPidl()
   {
      Delete();
   }

   AIDPidl(const AIDPidl& pidl)
   {
      m_p = ILCloneFull(pidl.m_p);
   }

   AIDPidl(PCIDLIST_ABSOLUTE pidl)
   {
      m_p = ILCloneFull(pidl);
   }

   //AIDPidl(PCUIDLIST_RELATIVE pidl)
   //{
   //   m_p = ILCloneFull((PCUIDLIST_ABSOLUTE) pidl);
   //}

   //AIDPidl(PCUITEMID_CHILD pidl)
   //{
   //   m_p = (PIDLIST_ABSOLUTE) ILCloneChild(pidl);
   //}

   AIDPidl(PCIDLIST_ABSOLUTE pidl, PCUIDLIST_RELATIVE pidlPath)
   {
      m_p = ::ILCombine(pidl, pidlPath);
   }

   AIDPidl(PCIDLIST_ABSOLUTE pidl, PCUIDLIST_RELATIVE pidlPath, PCUITEMID_CHILD pidlChild)
   {
      m_p = ::ILCombine(pidl, pidlPath);
      m_p = (PIDLIST_ABSOLUTE) ::ILAppendID(m_p, (LPSHITEMID) pidlChild, TRUE);
   }

   // Operations

   void Delete()
   {
      if( m_p != NULL ) ::ILFree(m_p);
      m_p = NULL;
   }

   BOOL Create(LPSHITEMID pSrcItem)
   {
      Delete();
      m_p = (PIDLIST_ABSOLUTE) ::ILAppendID(NULL, pSrcItem, TRUE);
      return m_p != NULL;
   }

   BOOL CreateDelegate(IMalloc* pMalloc, LPSHITEMID pSrcItem)
   {
      Delete();
      // Returns a PDELEGATEITEMID
      LPBYTE pDelegate = (LPBYTE) pMalloc->Alloc(pSrcItem->cb);
      if( pDelegate == NULL ) return FALSE;
      ::CopyMemory(pDelegate + 4, pSrcItem, pSrcItem->cb);
      m_p = reinterpret_cast<PIDLIST_ABSOLUTE>(pDelegate);
      return TRUE;
   }

   BOOL CreateFromPath(LPCSTR pstrFilename)
   {
      Delete();
      m_p = ::ILCreateFromPathA(pstrFilename);
      return m_p != NULL;
   }

   BOOL CreateFromPath(LPCWSTR pstrFilename)
   {
      Delete();
      m_p = ::ILCreateFromPathW(pstrFilename);
      return m_p != NULL;
   }

   BOOL CreateFromObject(IUnknown* pUnk)
   {
      Delete();
      return ::SHGetIDListFromObject(pUnk, &m_p) == S_OK;
   }

   BOOL CreateFromDisplayName(PCWSTR pszName, IBindCtx* pbc = NULL)
   {
      Delete();
      SFGAOF FlagsOut = 0;
      return ::SHParseDisplayName(pszName, pbc, &m_p, 0, &FlagsOut) == S_OK;
   }

   BOOL CreateFromKnownFolder(REFKNOWNFOLDERID FolderId, DWORD dwFlags = 0, HANDLE hToken = NULL)
   {
      Delete();
      return ::SHGetKnownFolderIDList(FolderId, dwFlags, hToken, &m_p) == S_OK;
   }

   BOOL Copy(PCIDLIST_ABSOLUTE pidl)
   {
      Delete();
      m_p = ILCloneFull(pidl);
      return m_p != NULL;
   }

   //BOOL Copy(PCIDLIST_RELATIVE pidl)
   //{
   //   Delete();
   //   m_p = ILCloneFull((PCIDLIST_ABSOLUTE) pidl);
   //   return m_p != NULL;
   //}

   BOOL Copy(PCUITEMID_CHILD_ARRAY rgpidl, int cidl)
   {
      Delete();
      for( int i = 0; i < cidl; i++ ) m_p = (PIDLIST_ABSOLUTE) ::ILAppendID(m_p, (LPSHITEMID) rgpidl[i], TRUE);
      return m_p != NULL;
   }

   BOOL Append(PCUIDLIST_RELATIVE pidl)
   {
      PIDLIST_ABSOLUTE p = ::ILCombine(m_p, pidl);
      if( m_p != NULL ) ::ILFree(m_p);
      m_p = p;
      return p != NULL;
   }

   //BOOL Append(PCUITEMID_CHILD pidl)
   //{
   //   ATLASSERT(pidl);
   //   m_p = (PIDLIST_ABSOLUTE) ::ILAppendID(m_p, (LPSHITEMID) pidl, TRUE);
   //   return m_p != NULL;
   //}

   BOOL Append(const AIDPidl& pidl)
   {
      return Append((PCUIDLIST_RELATIVE) pidl.m_p);
   }

   BOOL AppendSHItemID(LPSHITEMID pSHItemID, BOOL bAppend = TRUE)
   {
      ATLASSERT(pSHItemID != NULL);
      m_p = (PIDLIST_ABSOLUTE) ::ILAppendID(m_p, pSHItemID, bAppend);
      return m_p != NULL;
   }

   BOOL IsEqual(PCIDLIST_ABSOLUTE pidl) const
   {
      ATLASSERT(m_p != NULL);
      return ::ILIsEqual(m_p, pidl);
   }

   BOOL IsSamePIDL(PCIDLIST_ABSOLUTE pidl) const
   {
      IShellFolder* pFolder = NULL;
      if( FAILED( ::SHGetDesktopFolder(&pFolder) ) ) return FALSE;
      HRESULT Hr = pFolder->CompareIDs(0, (PCUIDLIST_RELATIVE) m_p, (PCUIDLIST_RELATIVE) pidl);
      pFolder->Release();
      return (SUCCEEDED(Hr) && HRESULT_CODE(Hr) == 0);
   }

   AIDPidl& operator =(const AIDPidl& pidl)
   {
      Copy(pidl.m_p);
      return *this;
   }

   AIDPidl& operator =(PCIDLIST_ABSOLUTE pidl)
   {
      Copy(pidl);
      return *this;
   }

   bool operator ==(const AIDPidl& pidl) const
   {
      return IsEqual(pidl.m_p) == TRUE;
   }

   bool operator ==(PCIDLIST_ABSOLUTE pidl) const
   {
      return IsEqual(pidl) == TRUE;
   }

   bool operator !=(const AIDPidl& pidl) const
   {
      return IsEqual(pidl.m_p) != TRUE;
   }

   bool operator !=(PCIDLIST_ABSOLUTE pidl) const
   {
      return IsEqual(pidl) != TRUE;
   }

   AIDPidl& operator +=(const AIDPidl& pidl)
   {
      Append(pidl.m_p);
      return *this;
   }

   AIDPidl& operator +=(PCUIDLIST_ABSOLUTE pidl)
   {
      Append(pidl);
      return *this;
   }

   //AIDPidl& operator +=(PCUIDLIST_RELATIVE pidl)
   //{
   //   Append(pidl);
   //   return *this;
   //}

   PIDLIST_RELATIVE* operator&()
   {
      return (PIDLIST_RELATIVE*) &m_p;
   }

   operator PIDLIST_RELATIVE() const
   {
      return m_p;
   }

   //operator PIDLIST_ABSOLUTE() const
   //{
   //   return m_p;
   //}

   bool operator!() const
   {
      return IsEmpty() == TRUE;
   }

   PIDLIST_ABSOLUTE GetData() const
   {
      return m_p;
   }

   PCUIDLIST_RELATIVE GetNextItem() const
   {
      ATLASSERT(m_p != NULL);
      return ::ILGetNext((PCUIDLIST_RELATIVE) m_p);
   }

   BOOL IsNull() const
   {
      return m_p == NULL;
   }

   BOOL IsEmpty() const
   {
      return ILIsEmpty((PCUIDLIST_RELATIVE) m_p);
   }

   UINT GetByteSize() const
   {
      return ::ILGetSize((PCUIDLIST_RELATIVE) m_p);
   }

   BOOL RemoveLastItem()
   {
      ATLASSERT(m_p != NULL);
      return ::ILRemoveLastID((PUIDLIST_RELATIVE) m_p);
   }

   BOOL IsParent(PCIDLIST_ABSOLUTE pidl, BOOL bImmediate = TRUE) const
   {
      ATLASSERT(m_p != NULL);
      return ::ILIsParent(m_p, pidl, bImmediate);
   }

   PUITEMID_CHILD GetLastItem() const
   {
      ATLASSERT(m_p != NULL);
      return ::ILFindLastID((PCUIDLIST_RELATIVE) m_p);
   }

   // Returns a pointer to the pidl(s) beginning at the position.
   // Actual return-type is PUIDLIST_RELATIVE!
   //PUITEMID_CHILD GetItem(int iIndex) const
   //{
   //   if( iIndex < 0 ) return NULL;
   //   if( ::ILIsEmpty(m_p) ) return NULL;
   //   if( iIndex == 0 ) return (PUITEMID_CHILD) m_p;
   //   PUIDLIST_RELATIVE pidlItem = ::ILGetNext((PCUIDLIST_RELATIVE) m_p);
   //   while( !::ILIsEmpty(pidlItem) ) {
   //      if( --iIndex <= 0 ) return (PUITEMID_CHILD) pidlItem;
   //      pidlItem = ::ILGetNext(pidlItem);
   //   }
   //   return NULL;
   //}

   HRESULT CopyTo(PIDLIST_ABSOLUTE* ppPidl) const
   {
      ATLASSERT(ppPidl != NULL);
      ATLASSERT(*ppPidl == NULL);
      *ppPidl = ILCloneFull(m_p);
      return S_OK;
   }

   BOOL IsChild() const
   {
      return ILIsChild((PCUIDLIST_RELATIVE) m_p);
   }

   PUIDLIST_RELATIVE FindChild(PCIDLIST_ABSOLUTE pidl) const
   {
      ATLASSERT(m_p != NULL);
      return ::ILFindChild(m_p, pidl);
   }

   UINT GetItemCount() const
   {
      if( IsEmpty() ) return 0;
      UINT nCount = 0;
      PCUIDLIST_RELATIVE pidlItem = m_p;
      while( (pidlItem = ::ILGetNext(pidlItem)) != NULL ) nCount++;
      return nCount;
   }

   void Attach(PIDLIST_ABSOLUTE pidl)
   {
      Delete();
      m_p = pidl;
   }

   //void Attach(PIDLIST_RELATIVE pidl)
   //{
   //   Delete();
   //   m_p = (PIDLIST_ABSOLUTE) pidl;
   //}

   PIDLIST_ABSOLUTE Detach()
   {
      PIDLIST_ABSOLUTE pidlItem = m_p;
      m_p = NULL;
      return pidlItem;
   }

   BOOL IsAligned() const
   {
      return ILIsAligned((PCUIDLIST_RELATIVE) m_p);
   }

   PITEMID_CHILD CloneItem(int iIndex)
   {
      PUIDLIST_RELATIVE pidlItem = (PUIDLIST_RELATIVE) m_p;
      while( --iIndex >= 0 && pidlItem != NULL ) pidlItem = ::ILGetNext(pidlItem);
      if( pidlItem != NULL ) return ILCloneChild( (PUITEMID_CHILD) pidlItem );
      return NULL;
   }

   HRESULT LoadFromStream(IStream* pStream)
   {
      ATLASSERT(pStream != NULL);
      Delete();
      return ::ILLoadFromStream(pStream, (PIDLIST_RELATIVE*) &m_p);
   }

   HRESULT SaveToStream(IStream* pStream)
   {
      ATLASSERT(pStream != NULL);
      return ::ILSaveToStream(pStream, (PIDLIST_RELATIVE) m_p);
   }

   HRESULT GetName(SIGDN sigdnName, LPWSTR* ppszName)
   {
      ATLASSERT(m_p != NULL);
      return ::SHGetNameFromIDList(m_p, sigdnName, ppszName);
   }

   friend AIDPidl __stdcall operator +(const AIDPidl& string1, const AIDPidl& string2);
};

// AIDPidl "operator +" function

inline AIDPidl __stdcall operator +(const AIDPidl& pidl1, const AIDPidl& pidl2)
{
   AIDPidl pidl(pidl1);
   pidl.Append(pidl2);
   return pidl;
}

// AIDPidl helpers

inline VOID SHChangeNotify(LONG wEventId, UINT uFlags, const AIDPidl& pidl)
{
   ATLASSERT((uFlags & SHCNF_TYPE) == SHCNF_IDLIST);
   ATLASSERT(!pidl.IsNull());
   ::SHChangeNotify(wEventId, uFlags, pidl.GetData(), NULL);
}

inline VOID SHChangeNotify(LONG wEventId, UINT uFlags, const AIDPidl& pidl1, const AIDPidl& pidl2)
{
   ATLASSERT((uFlags & SHCNF_TYPE) == SHCNF_IDLIST);
   ATLASSERT(!pidl1.IsNull() && !pidl2.IsNull());
   ::SHChangeNotify(wEventId, uFlags, pidl1.GetData(), pidl2.GetData());
}
